package dirty

import (
	"fmt"
	"reflect"
	"unicode"
)

func getStructPair(e Element) Array {
	if e.getType() != ElemArray && len(e.(Array)) != 2 {
		// todo error: not a pair
		fmt.Println("not a pair")
		return nil
	}
	return e.(Array)
}

func getStructFieldName(pair Array) string {
	k := reflect.TypeOf(pair[0]).Kind()
	if k != reflect.String {
		// todo error: name not string
		fmt.Println("name not string")
		return ""
	}
	fieldName := string(pair[0].(String))
	runes := []rune(fieldName)
	runes[0] = unicode.ToUpper(runes[0])
	fieldName = string(runes)
	return fieldName
}

func getStructMapKey(pair Array, mk reflect.Kind) reflect.Value {
	sk := reflect.ValueOf(pair[0]).Type().String()
	if (sk == "dirty.String" && mk != reflect.String) ||
		(sk == "dirty.Int" && mk != reflect.Int && mk != reflect.Int8 &&
			mk != reflect.Int16 && mk != reflect.Int32 && mk != reflect.Int64 &&
			mk != reflect.Uint && mk != reflect.Uint8 &&
			mk != reflect.Uint16 && mk != reflect.Uint32 && mk != reflect.Uint64) ||
		(sk == "dirty.Const" && mk != reflect.Bool) /*test sk ==dirty.Const but not dirty::Bool */ {
		// todo error
		fmt.Printf("pair[0] (%v) not map key (%v)\n", sk, mk)
		return reflect.ValueOf(nil)
	}
	switch mk {
	case reflect.String:
		return reflect.ValueOf(string(pair[0].(String)))
	case reflect.Bool:
		return reflect.ValueOf(pair[0].(Const).Bool())
	case reflect.Uint:
		return reflect.ValueOf(uint(pair[0].(Int)))
	case reflect.Uint8:
		return reflect.ValueOf(uint8(pair[0].(Int)))
	case reflect.Uint16:
		return reflect.ValueOf(uint16(pair[0].(Int)))
	case reflect.Uint32:
		return reflect.ValueOf(uint32(pair[0].(Int)))
	case reflect.Uint64:
		return reflect.ValueOf(uint64(pair[0].(Int)))
	case reflect.Int:
		return reflect.ValueOf(int(pair[0].(Int)))
	case reflect.Int8:
		return reflect.ValueOf(int8(pair[0].(Int)))
	case reflect.Int16:
		return reflect.ValueOf(int16(pair[0].(Int)))
	case reflect.Int32:
		return reflect.ValueOf(int32(pair[0].(Int)))
	case reflect.Int64:
		return reflect.ValueOf(int64(pair[0].(Int)))
	default:
		return reflect.ValueOf(nil)
	}
}

func setStructValue(value Element, f reflect.Value) error {
	switch value.getType() {
	case ElemArray:
		err := convertStruct(value.(Array), f)
		if err != nil {
			return err
		}
	case ElemConst:
		if value == NULL && f.Kind() == reflect.Ptr {
			// do nothing; default value is nil
		} else if (value == TRUE || value == FALSE) && f.Kind() == reflect.Bool {
			f.SetBool(value.(Const).Bool())
		} else {
			// todo error: type mismatch
			fmt.Println("type mismatch")
			return nil
		}
	case ElemFloat:
		if f.Kind() == reflect.Float64 || f.Kind() == reflect.Float32 {
			f.SetFloat(float64(value.(Float)))
		} else {
			// todo error: type mismatch
			fmt.Println("type mismatch")
			return nil
		}
	case ElemInt:
		if f.Kind() == reflect.Int || f.Kind() == reflect.Int8 || f.Kind() == reflect.Int16 || f.Kind() == reflect.Int32 || f.Kind() == reflect.Int64 {
			f.SetInt(int64(value.(Int)))
		} else {
			// todo error: type mismatch
			fmt.Println("type mismatch")
			return nil
		}
	case ElemString:
		if f.Kind() == reflect.String {
			f.SetString(string(value.(String)))
		} else {
			// todo error: type mismatch
			fmt.Println("type mismatch")
			return nil
		}
	default:
		// todo error: unknown type
		fmt.Println("unknown type")
		return nil
	}
	return nil
}

func convertStruct(array Array, s reflect.Value) error {
	kind := s.Kind()
	if kind == reflect.Struct {
		for _, e := range array {
			pair := getStructPair(e)
			fieldName := getStructFieldName(pair)

			f := s.FieldByName(fieldName)
			if !f.IsValid() {
				// todo error no such field
				fmt.Println("no such field", fieldName)
				return nil
			}
			if f.Kind() == reflect.Ptr && pair[1] != NULL {
				f.Set(reflect.New(f.Type().Elem()))
				f = f.Elem()
			}
			setStructValue(pair[1], f)
		}
		//fmt.Printf("%+v\n", s)
		return nil
	}
	if kind == reflect.Slice {
		elemType := s.Type().Elem()
		if s.Len() != 0 {
			// todo error slice not empty
			fmt.Println("slice len != 0")
			return nil
		}
		capacity := s.Cap()
		s.SetLen(capacity)
		s2 := s
		for i, e := range array {
			if i < capacity {
				f := s2.Index(i)
				setStructValue(e, f)
			} else {
				f := reflect.New(elemType).Elem()
				setStructValue(e, f)
				s2 = reflect.Append(s2, f)
			}
		}
		s.Set(s2)
		//fmt.Printf("%+v\n", s)
		return nil
	}
	if kind == reflect.Array {
		if s.Len() != len(array) {
			// todo error array len not Array len
			fmt.Println("array len not length of Array")
			return nil
		}
		for i, e := range array {
			f := s.Index(i)
			setStructValue(e, f)
		}
		//fmt.Printf("%+v\n", s)
		return nil
	}
	if kind == reflect.Map {
		keyType := s.Type().Key()
		keyKind := keyType.Kind()
		valueType := s.Type().Elem()
		for _, e := range array {
			pair := getStructPair(e)
			k := getStructMapKey(pair, keyKind)
			v := reflect.New(valueType).Elem()
			setStructValue(pair[1], v)
			if s.IsNil() {
				var mapType = reflect.MapOf(keyType, valueType)
				s.Set(reflect.MakeMapWithSize(mapType, 0))
			}
			s.SetMapIndex(k, v)
		}
		//fmt.Printf("%+v\n", s)
		return nil
	}
	//fmt.Println(kind)
	return nil
}
